﻿#region Copyright
// 
// Copyright (C) 2008 VirtualStaticVoid <virtualstaticvoid@gmail.com>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using NAom.Core;

namespace NAom.Model
{
  public class AomModel
  {

    private static readonly Type __genericDynamicTypeType = typeof(DynamicType<>);
    private static readonly Type __genericPropertyTypeType = typeof(PropertyType<>);

    private readonly IDictionary<string, DynamicTypeEntry<EntityType>> _entityTypes;
    private readonly IDictionary<string, DynamicTypeEntry<RelationType>> _relationTypes;

    internal AomModel(AomModelBuilder modelBuilder)
    {
      if (modelBuilder == null) throw new ArgumentNullException("modelBuilder");

      _entityTypes = new Dictionary<string, DynamicTypeEntry<EntityType>>();
      _relationTypes = new Dictionary<string, DynamicTypeEntry<RelationType>>();

      LoadEntityTypes(modelBuilder);
      LoadRelationTypes(modelBuilder);

    }

    private void LoadEntityTypes(AomModelBuilder modelBuilder)
    {
      foreach (EntityType entityType in modelBuilder.EntityTypes)
      {
        _entityTypes.Add(entityType.Name, new DynamicTypeEntry<EntityType>(entityType, CreateDynamicTypeFactory(entityType)));
      }
    }

    private void LoadRelationTypes(AomModelBuilder modelBuilder)
    {
      foreach (RelationType relationType in modelBuilder.RelationTypes)
      {
        _relationTypes.Add(relationType.Name, new DynamicTypeEntry<RelationType>(relationType, CreateDynamicTypeFactory(relationType)));
      }
    }

    private static IDynamicTypeFactory CreateDynamicTypeFactory(ITypeDefinition typeDefinition)
    {
      Type dynamicTypeType = __genericDynamicTypeType.MakeGenericType(typeDefinition.BaseType);
      IDynamicType dynamicType = (IDynamicType)Activator.CreateInstance(dynamicTypeType, typeDefinition.Name);
      LoadAttributeTypes(typeDefinition, dynamicType);
      return dynamicType.CreateFactory();
    }

    private static void LoadAttributeTypes(ITypeDefinition typeDefinition, IDynamicType dynamicType)
    {
      foreach (AttributeType attributeType in typeDefinition.AttributeTypes)
      {
        Type propertyTypeType = __genericPropertyTypeType.MakeGenericType(attributeType.DataType);
        IPropertyType propertyType = (IPropertyType)Activator.CreateInstance(propertyTypeType, attributeType.Name);
        dynamicType.Properties.Add(propertyType);
      }
    }

    public TEntity CreateInstance<TEntity>(EntityType entityType, params object[] ctorArgs)
      where TEntity : Entity
    {
      if (entityType == null) throw new ArgumentNullException("entityType");

      return (TEntity)_entityTypes[entityType.Name].DynamicTypeFactory.CreateInstance(ctorArgs);
    }

    public IEnumerable<EntityType> EntityTypes
    {
      get { return _entityTypes.Values.AsEnumerable().Select(e => e.TypeDefinition); }
    }

    public EntityType EntityType(string entityTypeName)
    {
      if (String.IsNullOrEmpty(entityTypeName)) throw new ArgumentNullException("entityTypeName");

      return _entityTypes[entityTypeName].TypeDefinition;
    }

    public TRelation CreateInstance<TRelation>(RelationType relationType, EntityType leftEntity, EntityType rightEntity, params object[] ctorArgs)
      where TRelation : Relation
    {
      if (relationType == null) throw new ArgumentNullException("relationType");
      if (leftEntity == null) throw new ArgumentNullException("leftEntity");
      if (rightEntity == null) throw new ArgumentNullException("rightEntity");

      object[] args = new object[ctorArgs.Length + 2];
      args[0] = leftEntity;
      args[1] = rightEntity;
      ctorArgs.CopyTo(args, 2);

      return (TRelation)_relationTypes[relationType.Name].DynamicTypeFactory.CreateInstance(ctorArgs);
    }

    public RelationType RelationType(string relationTypeName)
    {
      if (String.IsNullOrEmpty(relationTypeName)) throw new ArgumentNullException("relationTypeName");

      return _relationTypes[relationTypeName].TypeDefinition;
    }

    public IEnumerable<RelationType> RelationTypes
    {
      get { return _relationTypes.Values.AsEnumerable().Select(e => e.TypeDefinition); }
    }

    class DynamicTypeEntry<TTypeDefinition>
      where TTypeDefinition : ITypeDefinition
    {

      public DynamicTypeEntry(TTypeDefinition typeDefinition, IDynamicTypeFactory dynamicTypeFactory)
      {
        TypeDefinition = typeDefinition;
        DynamicTypeFactory = dynamicTypeFactory;
      }

      public TTypeDefinition TypeDefinition { get; private set; }
      public IDynamicTypeFactory DynamicTypeFactory { get; private set; }

    }

  }
}
